package com.hmdp.controller;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.entity.RedisData;
import com.hmdp.entity.Shop;
import com.hmdp.service.IShopService;
import com.hmdp.utils.CacheClient;
import com.hmdp.utils.SystemConstants;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import com.hmdp.utils.RedisConstants;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@RestController
@Api(tags = "店铺接口")
@RequestMapping("/shop")
public class ShopController {

    @Resource
    public IShopService shopService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CacheClient cacheClient;


    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
//    @GetMapping("/{id}")
//    public Result queryShopById(@PathVariable("id") Long id) {
//        String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//        //2.判断是否存在
//        if (StrUtil.isNotBlank(shopJson)) {
//            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
//            //3.若存在直接返回数据
//            return Result.ok(shop);
//        }
//        if(shopJson != null){  //判断 ""
//            return Result.fail("店铺不存在");
//        }
//
//        //4.从数据库中根据id查数据
//        Shop shop = shopService.getById(id);
//        //5.若不存在，写入redis空值
//        if (shop == null){
//            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
//        }
//        //6.将数据写入redis
//        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop));
//        stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY  + id,30, TimeUnit.MINUTES);
//
//        //7.返回数据
//        return Result.ok(shop);
//    }


    /*
    * 互斥锁查询商品
    * */
//    @GetMapping("/{id}")
//    public Result queryShopById(@PathVariable("id") Long id) {
//        Result chacheById = getChacheById(id);
//        if(chacheById!=null){
//            return chacheById;
//        }
//        try{
//            String lockKey= RedisConstants.LOCK_SHOP_KEY + id;
//            boolean isLock = tryLock(lockKey);
//            if(!isLock){
//                //休眠
//                Thread.sleep(50);
//                return getChacheById(id);
//            }
//            Result result = getChacheById(id);
//            if(result!=null){
//                return result;
//            }
//            Shop shop= shopService.getById(id);
//            if(Objects.isNull(shop)){
//                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
//                return Result.fail("店铺不存在");
//            }
//
//            //数据库存在
//            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);
//            return Result.ok(shop);
//
//        }catch (Exception e){
//            return Result.fail("服务器异常");
//        }
//        finally {
//            unLock(RedisConstants.LOCK_SHOP_KEY + id);
//        }
//
//
//    }
//
//    private Result getChacheById(Long id){
//        String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
//        //2.判断是否存在
//        if (StrUtil.isNotBlank(shopJson)) {
//            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
//            //3.若存在直接返回数据
//            return Result.ok(shop);
//        }
//        // 判断缓存中查询的数据是否是空字符串(isNotBlank把 null 和 空字符串 给排除了)
//        if (Objects.nonNull(shopJson)) {
//            // 当前数据是空字符串，说明缓存也命中了（该数据是之前缓存的空对象），直接返回失败信息
//            return Result.fail("店铺不存在");
//        }
//        // 缓存未命中（缓存数据既没有值，又不是空字符串）
//        return null;
//    }
//
//    private boolean  tryLock(String key){
//        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
//        return Boolean.TRUE.equals(flag);
//    }
//
//    private void unLock(String key){
//        stringRedisTemplate.delete(key);
//    }
//
//    /**
//     * 缓存重建线程池
//     */
//    public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
//
//    /*
//     * 逻辑锁实现查询商品
//     * */
//    @GetMapping("/{id}")
//    public Result queryShopById(@PathVariable("id") Long id) {
//        //拿到key
//        String key= RedisConstants.CACHE_SHOP_KEY + id;
//        String shopJson = stringRedisTemplate.opsForValue().get(key);
//        if(StrUtil.isBlank(shopJson)){
//            return Result.fail("店铺不存在");
//        }
//        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
//
//
//        // 这里需要先转成JSONObject再转成反序列化，否则可能无法正确映射Shop的字段
//        JSONObject data = (JSONObject) redisData.getData();
//        Shop shop = JSONUtil.toBean(data, Shop.class);
//        LocalDateTime expireTime = redisData.getExpireTime();
//        if (expireTime.isAfter(LocalDateTime.now())) {
//            // 当前缓存数据未过期，直接返回
//            return Result.ok(shop);
//        }
//
//        // 2、缓存数据已过期，获取互斥锁，并且重建缓存
//        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
//        boolean isLock = tryLock(lockKey);
//        if (isLock) {
//            // 获取锁成功，开启一个子线程去重建缓存
//            CACHE_REBUILD_EXECUTOR.submit(() -> {
//                try {
//                    this.saveShopToCache(id, RedisConstants.CACHE_SHOP_LOGICAL_TTL);
//                } finally {
//                    unLock(lockKey);
//                }
//            });
//        }
//
//        // 3、获取锁失败，再次查询缓存，判断缓存是否重建（这里双检是有必要的）
//        shopJson = stringRedisTemplate.opsForValue().get(key);
//        if (StrUtil.isBlank(shopJson)) {
//            // 3.1 缓存未命中，直接返回失败信息
//            return Result.fail("店铺数据不存在");
//        }
//        // 3.2 缓存命中，将JSON字符串反序列化未对象，并判断缓存数据是否逻辑过期
//        redisData = JSONUtil.toBean(shopJson, RedisData.class);
//        // 这里需要先转成JSONObject再转成反序列化，否则可能无法正确映射Shop的字段
//        data = (JSONObject) redisData.getData();
//        shop = JSONUtil.toBean(data, Shop.class);
//        expireTime = redisData.getExpireTime();
//        if (expireTime.isAfter(LocalDateTime.now())) {
//            // 当前缓存数据未过期，直接返回
//            return Result.ok(shop);
//        }
//
//        // 4、返回过期数据
//        return Result.ok(shop);
//    }
//
//    /**
//     * 将数据保存到缓存中
//     *
//     * @param id            商铺id
//     * @param expireSeconds 逻辑过期时间
//     */
//    public void saveShopToCache(Long id, Long expireSeconds) {
//        // 从数据库中查询店铺数据
//        Shop shop = shopService.getById(id);
//        // 封装逻辑过期数据
//        RedisData redisData = new RedisData();
//        redisData.setData(shop);
//        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
//        // 将逻辑过期数据存入Redis中
//        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
//    }


    @GetMapping("/{id}")
    public Result queryById(@PathVariable Long id) {
        // 调用解决缓存穿透的方法
//        System.out.println(id);
//        Shop shop = cacheClient.handleCachePenetration(RedisConstants.CACHE_SHOP_KEY, id, Shop.class,
//                id2->shopService.getById(id), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//        if (Objects.isNull(shop)){
//            return Result.fail("店铺不存在");
//        }

        // 调用解决缓存击穿的方法
        Shop shop = cacheClient.handleCacheBreakdown(RedisConstants.CACHE_SHOP_KEY, id, Shop.class,
                id2 -> shopService.getById(id), RedisConstants.CACHE_SHOP_TTL, TimeUnit.SECONDS);
        if (Objects.isNull(shop)) {
            return Result.fail("店铺不存在");
        }

        return Result.ok(shop);
    }




    /**
     * 新增商铺信息
     * @param shop 商铺数据
     * @return 商铺id
     */
    @PostMapping
    public Result saveShop(@RequestBody Shop shop) {
        //先更新数据库
        shopService.save(shop);
        //再写入redis
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + shop.getId()
                ,JSONUtil.toJsonStr(shop)
                ,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop.getId());
    }

    /**
     * 更新商铺信息
     * @param shop 商铺数据
     * @return 无
     */
    @PutMapping
    @Transactional
    public Result updateShop(@RequestBody Shop shop) {
        // 写入数据库
        shopService.updateById(shop);
        stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + shop.getId());
        return Result.ok();
    }

    /**
     * 根据商铺类型分页查询商铺信息
     * @param typeId 商铺类型
     * @param current 页码
     * @return 商铺列表
     */
    @GetMapping("/of/type")
    public Result queryShopByType(
            @RequestParam("typeId") Integer typeId,
            @RequestParam(value = "current", defaultValue = "1") Integer current
    ) {
        // 根据类型分页查询
        Page<Shop> page = shopService.query()
                .eq("type_id", typeId)
                .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
        // 返回数据
        return Result.ok(page.getRecords());
    }

    /**
     * 根据商铺名称关键字分页查询商铺信息
     * @param name 商铺名称关键字
     * @param current 页码
     * @return 商铺列表
     */
    @GetMapping("/of/name")
    public Result queryShopByName(
            @RequestParam(value = "name", required = false) String name,
            @RequestParam(value = "current", defaultValue = "1") Integer current
    ) {
        // 根据类型分页查询
        Page<Shop> page = shopService.query()
                .like(StrUtil.isNotBlank(name), "name", name)
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 返回数据
        return Result.ok(page.getRecords());
    }
}
